package org.acm.seguin.refactor.type;
import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import org.acm.seguin.refactor.AddImportTransform;
import org.acm.seguin.refactor.ComplexTransform;
import org.acm.seguin.refactor.RemoveImportTransform;
import org.acm.seguin.refactor.TransformAST;
import org.acm.seguin.summary.FieldAccessSummary;
import org.acm.seguin.summary.FieldSummary;
import org.acm.seguin.summary.FileSummary;
import org.acm.seguin.summary.ImportSummary;
import org.acm.seguin.summary.LocalVariableSummary;
import org.acm.seguin.summary.MessageSendSummary;
import org.acm.seguin.summary.MethodSummary;
import org.acm.seguin.summary.PackageSummary;
import org.acm.seguin.summary.ParameterSummary;
import org.acm.seguin.summary.Summary;
import org.acm.seguin.summary.TraversalVisitor;
import org.acm.seguin.summary.TypeDeclSummary;
import org.acm.seguin.summary.TypeSummary;
import org.acm.seguin.summary.VariableSummary;
/**
* Scans through the summary objects to create a list of files that reference
* a particular class.
*
*@author Chris Seguin
*/
public abstract class TypeChangeVisitor extends TraversalVisitor {
// Instance Variables
private ComplexTransform refactoring;
/**
* Visitor for type changes
*
*@param complex Description of Parameter
*/
public TypeChangeVisitor(ComplexTransform complex) {
refactoring = complex;
}
/**
* Visit a summary node. This is the default method.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(Summary node, Object data) {
// Shouldn't have to do anything about one of these nodes
return data;
}
/**
* Visit a file summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(FileSummary node, Object data) {
if (node.getFile() == null) {
return null;
}
if (!preconditions(node)) {
return null;
}
refactoring.clear();
LinkedList list = getAppropriateClasses(node);
Iterator iter = list.iterator();
while (iter.hasNext()) {
// Get the name of the class
String className = (String) iter.next();
// First check to see if any of the classes were imported
boolean foundImport = checkImports(node, className);
// Now we get down to the business of checking individual types
if (checkTypes(node, getState(foundImport, node, className))) {
AddImportTransform ait = getNewImports(node, className);
if (ait != null) {
ait.setIgnorePackageName(true);
refactoring.add(ait);
}
addRenamingTransforms(refactoring, node, className);
}
}
refactoring.add(getFileSpecificTransform(node));
if (refactoring.hasAnyChanges()) {
File oldFile = node.getFile();
File newFile = getNewFile(node);
System.out.println("Updating: " + oldFile.getName());
refactoring.add(new RemoveSamePackageTransform());
refactoring.apply(oldFile, newFile);
}
// Return some value
return refactoring;
}
/**
* Visit a import summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(ImportSummary node, Object data) {
// Local Variables
boolean importedClass = false;
boolean importedPackage = false;
boolean gettingPackage = node.getPackage().getName().equals(getCurrentPackage());
// Check to see if we have a specific class
if (gettingPackage) {
String typeName = node.getType();
if (typeName == null) {
importedPackage = true;
}
else {
String className = (String) data;
importedClass = (className.equals(typeName));
}
}
// At this point we know if we specifically imported the class
if (importedClass) {
refactoring.add(getRemoveImportTransform(node));
}
// Return an integer code for what was found in this import
return new Boolean(importedPackage || importedClass);
}
/**
* Visit a type summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(TypeSummary node, Object data) {
Boolean result = new Boolean(false);
// Check extension
TypeDeclSummary parent = node.getParentClass();
if (parent != null) {
result = (Boolean) parent.accept(this, data);
}
if (result.booleanValue()) {
return result;
}
// Check list of implemented interfaces
Iterator iter = node.getImplementedInterfaces();
if (iter != null) {
while (iter.hasNext()) {
TypeDeclSummary next = (TypeDeclSummary) iter.next();
result = (Boolean) next.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
}
// Over the fields
iter = node.getFields();
if (iter != null) {
while (iter.hasNext()) {
FieldSummary next = (FieldSummary) iter.next();
result = (Boolean) next.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
}
// Over the methods
iter = node.getMethods();
if (iter != null) {
while (iter.hasNext()) {
MethodSummary next = (MethodSummary) iter.next();
result = (Boolean) next.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
}
// Over the types
iter = node.getTypes();
if (iter != null) {
while (iter.hasNext()) {
TypeSummary next = (TypeSummary) iter.next();
result = (Boolean) next.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
}
// Return the last false value
return result;
}
/**
* Visit a method summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(MethodSummary node, Object data) {
Boolean result = new Boolean(false);
// Check the return type
TypeDeclSummary returnType = node.getReturnType();
if (returnType != null) {
result = (Boolean) returnType.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
// Check the parameters
Iterator iter = node.getParameters();
if (iter != null) {
while (iter.hasNext()) {
ParameterSummary next = (ParameterSummary) iter.next();
result = (Boolean) next.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
}
// Check the exceptions
iter = node.getExceptions();
if (iter != null) {
while (iter.hasNext()) {
TypeDeclSummary next = (TypeDeclSummary) iter.next();
result = (Boolean) next.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
}
// Check the dependencies
iter = node.getDependencies();
if (iter != null) {
while (iter.hasNext()) {
Summary next = (Summary) iter.next();
result = (Boolean) next.accept(this, data);
if (result.booleanValue()) {
return result;
}
}
}
return result;
}
/**
* Visit a field summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(FieldSummary node, Object data) {
return visit((VariableSummary) node, data);
}
/**
* Visit a parameter summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(ParameterSummary node, Object data) {
return visit((VariableSummary) node, data);
}
/**
* Visit a local variable summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(LocalVariableSummary node, Object data) {
return visit((VariableSummary) node, data);
}
/**
* Visit a variable summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(VariableSummary node, Object data) {
return node.getTypeDecl().accept(this, data);
}
/**
* Visit a type declaration summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(TypeDeclSummary node, Object data) {
// Local Variables
State state = (State) data;
boolean mustUsePackage = state.isPackageRequired();
String className = state.getClassName();
String nodePackageName = node.getPackage();
// Check if the package names match
if (isMatchingPackage(nodePackageName, mustUsePackage)) {
// Check for the specific type name
return new Boolean(className.equals(node.getType()));
}
return new Boolean(false);
}
/**
* Visit a message send summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(MessageSendSummary node, Object data) {
// Local Variables
State state = (State) data;
boolean mustUsePackage = state.isPackageRequired();
String className = state.getClassName();
// Check if the package names match
boolean classNameMatches =
(node.getObjectName() != null) &&
(node.getObjectName().equals(className));
boolean packageNameMatches = isMatchingPackage(node.getPackageName(), mustUsePackage);
return new Boolean(classNameMatches && packageNameMatches);
}
/**
* Visit a field access summary.
*
*@param node the summary that we are visiting
*@param data the data that was passed in
*@return the result
*/
public Object visit(FieldAccessSummary node, Object data) {
// Local Variables
State state = (State) data;
boolean mustUsePackage = state.isPackageRequired();
String className = state.getClassName();
boolean classNameMatches =
(node.getObjectName() != null) &&
(node.getObjectName().equals(className));
boolean packageNameMatches = isMatchingPackage(node.getPackageName(), mustUsePackage);
return new Boolean(classNameMatches && packageNameMatches);
}
/**
* Returns the state object to be used to determine if the particular type
* we are deleting is present
*
*@param foundImport Description of Parameter
*@param node Description of Parameter
*@param className Description of Parameter
*@return The State value
*/
protected State getState(boolean foundImport, FileSummary node, String className) {
boolean mustUsesFullPackageName = !(foundImport || isSamePackage(node));
return new State(className, mustUsesFullPackageName);
}
/**
* Gets the File Specific Transform
*
*@param summary Gets a file specific transform
*@return The FileSpecificTransform value
*/
protected abstract TransformAST getFileSpecificTransform(FileSummary summary);
/**
* Gets the New Imports transform
*
*@param node the file summary
*@param className the name of the class that is changing
*@return The NewImports value
*/
protected abstract AddImportTransform getNewImports(FileSummary node, String className);
/**
* Gets the Remove Imports transform
*
*@param node the import summary
*@return The transform
*/
protected abstract RemoveImportTransform getRemoveImportTransform(ImportSummary node);
/**
* Gets the list of classes to iterate over
*
*@param node the file summary
*@return The list
*/
protected abstract LinkedList getAppropriateClasses(FileSummary node);
/**
* Gets the reference to the file where the refactored output should be sent
*
*@param node the files summary
*@return The NewFile value
*/
protected abstract File getNewFile(FileSummary node);
/**
* Return the current package
*
*@return the current package of the class
*/
protected abstract String getCurrentPackage();
/**
* Checks any preconditions
*
*@param summary Description of Parameter
*@return Description of the Returned Value
*/
protected boolean preconditions(FileSummary summary) {
return true;
}
/**
* Gets the RenamingTransform
*
*@param refactoring the refactoring
*@param node the file summary to reference
*@param className the name of the class that is changing
*/
protected abstract void addRenamingTransforms(ComplexTransform refactoring,
FileSummary node, String className);
/**
* Returns true if the package is the same
*
*@param node the current node
*@return true if the object is in the package
*/
private boolean isSamePackage(FileSummary node) {
PackageSummary parent = (PackageSummary) node.getParent();
return parent.getName().equals(getCurrentPackage());
}
/**
* Determines if the package matches
*
*@param nodePackageName The node's package
*@param mustUsePackage must it use the full package name
*@return true if the package matches
*/
private boolean isMatchingPackage(String nodePackageName, boolean mustUsePackage) {
boolean nullPackageName = (nodePackageName == null);
if (mustUsePackage && nullPackageName) {
return false;
}
return nullPackageName || (nodePackageName.equals(getCurrentPackage()));
}
/**
* Determine if there was anything by that name imported
*
*@param node The file summary node
*@param data Data used for traversing the tree
*@return true if the data was imported
*/
private boolean checkImports(FileSummary node, Object data) {
// Iterate over the import statements
Iterator iter = node.getImports();
if (iter != null) {
while (iter.hasNext()) {
ImportSummary next = (ImportSummary) iter.next();
Object nodeReturn = next.accept(this, data);
if (((Boolean) nodeReturn).booleanValue()) {
return true;
}
}
}
// Not found in import statements
return false;
}
/**
* Description of the Method
*
*@param node the file summary node to traverse
*@param data data to help with the traversal
*@return return true if the types used the specified class
*/
private boolean checkTypes(FileSummary node, Object data) {
Iterator iter = node.getTypes();
if (iter != null) {
while (iter.hasNext()) {
TypeSummary next = (TypeSummary) iter.next();
if (((Boolean) next.accept(this, data)).booleanValue()) {
return true;
}
}
}
return false;
}
}